package timing.ukulele.gateway.config;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.security.*;
import lombok.extern.slf4j.Slf4j;
import org.springdoc.core.properties.AbstractSwaggerUiConfigProperties;
import org.springdoc.core.properties.SwaggerUiConfigProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.util.ObjectUtils;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static org.springdoc.core.utils.Constants.DEFAULT_API_DOCS_URL;

@Configuration
@Slf4j
@ConditionalOnProperty(
        prefix = "springdoc.api-docs", name = "enabled", havingValue = "true", matchIfMissing = true
)
public class DocumentationConfig {
    private final String LB_SCHEME = "lb";
    private final RouteDefinitionLocator locator;

    private final SwaggerUiConfigProperties swaggerUiConfigProperties;

    /**
     * 获取配置文件中默认配置的swagger组
     */
    private final Set<AbstractSwaggerUiConfigProperties.SwaggerUrl> defaultUrls;

    public DocumentationConfig(
            RouteDefinitionLocator locator,
            SwaggerUiConfigProperties swaggerUiConfigProperties) {
        this.locator = locator;
        this.swaggerUiConfigProperties = swaggerUiConfigProperties;
        this.defaultUrls = new HashSet<>();
    }

    @Bean
    public OpenAPI customOpenAPI() {
        // 基础信息
        Info info = new Info()
                .title("ukulele-api")
                .version("3.0.0")
                .description("SpringCloud快速开发框架")
                .termsOfService("http://www.timingtech.top")
                .license(new License().name("MIT").url("http://www.timingtech.top"));

        // 组件
        Components components = new Components();
        // 安全认证组件
        SecurityScheme securityScheme = new SecurityScheme();

        // 创建一个oauth认证流程
        OAuthFlows oAuthFlows = new OAuthFlows();
        // 设置OAuth2流程中认证服务的基本信息
        OAuthFlow oAuthFlow = new OAuthFlow()
                // 授权申请地址
                .authorizationUrl("http://www.ukulele.com:8080/auth/oauth2/authorize")
                // 获取token地址
                .tokenUrl("http://www.ukulele.com:8080/auth/oauth2/token")
                .scopes(new Scopes()
                        .addString("openid", "OpenId登录")
                        .addString("profile", "获取用户信息")
                );
        // 使用授权码模式
        oAuthFlows.authorizationCode(oAuthFlow);

        // OAuth2流程
        securityScheme.flows(oAuthFlows)
                .type(SecurityScheme.Type.OAUTH2);

        // 安全认证名
        String securityName = "Authenticate";
        // 将认证配置加入组件中
        components.addSecuritySchemes(securityName, securityScheme);

        SecurityRequirement securityRequirement = new SecurityRequirement();
        // 将安全认证和swagger-ui关联起来
        securityRequirement.addList(securityName);
        return new OpenAPI()
                // 基础描述信息
                .info(info)
                // 添加OAuth2认证流程组件
                .components(components)
                // 添加请求时携带OAuth2规范的请求头(通过OAuth2流程获取token后发请求时会自动携带Authorization请求头)
                .addSecurityItem(securityRequirement);
    }

    @EventListener(RefreshOpenApiEvent.class)
    public void refreshApis() {
        refreshGroup();
    }

    /**
     * 刷新swagger的group
     */
    public void refreshGroup() {
        // 获取网关路由
        List<RouteDefinition> definitions = locator.getRouteDefinitions().collectList().block();
        if (ObjectUtils.isEmpty(definitions)) {
            return;
        }

        // 根据路由规则生成 swagger组 配置
        Set<AbstractSwaggerUiConfigProperties.SwaggerUrl> swaggerUrls = definitions.stream()
                // 只处理在注册中心注册过的(lb://service)
                .filter(definition -> definition.getUri().getScheme().equals(LB_SCHEME))
                .map(definition -> {
                    // 生成 swagger组 配置，以微服务在注册中心中的名字当做组名、请求路径(我这里使用的是自动扫描生成的，所以直接用了这个，其它自定义的按需修改)
                    String authority = definition.getUri().getAuthority();
                    return new AbstractSwaggerUiConfigProperties.SwaggerUrl(authority, definition.getId() + DEFAULT_API_DOCS_URL, authority);
                })
                .collect(Collectors.toSet());

        // 如果在配置文件中有添加其它 swagger组 配置则将两者合并
        if (!ObjectUtils.isEmpty(defaultUrls)) {
            swaggerUrls.addAll(defaultUrls);
        }

        // 重置配置文件
        swaggerUiConfigProperties.setUrls(swaggerUrls);

        if (log.isDebugEnabled()) {
            String groups = swaggerUrls.stream()
                    .map(AbstractSwaggerUiConfigProperties.SwaggerUrl::getName)
                    .collect(Collectors.joining(","));
            log.debug("刷新Spring Gateway Doc Group成功，获取到组：{}.", groups);
        }
    }
}
